package com.ctrip.cap.common.shell;

import java.io.IOException;
import java.net.DatagramSocket;
import java.net.ServerSocket;
import java.util.NoSuchElementException;

/**
 * A simple rather userful port finder
 * 
 * @author ltyao
 *
 */
public class AvailablePortFinder {
	/**
	 * The minimum number of server port number.
	 */
	public static final int MIN_PORT_NUMBER = 40000;

	/**
	 * The maximum number of server port number.
	 */
	public static final int MAX_PORT_NUMBER = 60000;

	/**
	 * cache index for port binding delay
	 */
	private static int current = MIN_PORT_NUMBER;

	private AvailablePortFinder() {
	}

	/**
	 * Gets the next available port starting at the lowest port number.
	 *
	 * @throws NoSuchElementException
	 *             if there are no ports available
	 */
	public synchronized static int getNextAvailable() {
		if (++current == MAX_PORT_NUMBER) {
			current = MIN_PORT_NUMBER;
		}
		return getNextAvailable(current);
	}

	/**
	 * Gets the next available port starting at a port.
	 *
	 * @param fromPort
	 *            the port to scan for availability
	 * @throws NoSuchElementException
	 *             if there are no ports available
	 */
	private synchronized static int getNextAvailable(int fromPort) {
		if ((fromPort < MIN_PORT_NUMBER) || (fromPort > MAX_PORT_NUMBER)) {
			throw new IllegalArgumentException("Invalid start port: "
					+ fromPort);
		}

		for (int i = fromPort; i <= MAX_PORT_NUMBER; i++) {
			if (available(i)) {
				return i;
			}
			current++;
		}

		throw new NoSuchElementException("Could not find an available port "
				+ "above " + fromPort);
	}

	/**
	 * Checks to see if a specific port is available.
	 *
	 * @param port
	 *            the port to check for availability
	 */
	private static boolean available(int port) {
		if ((port < MIN_PORT_NUMBER) || (port > MAX_PORT_NUMBER)) {
			throw new IllegalArgumentException("Invalid start port: " + port);
		}

		ServerSocket ss = null;
		DatagramSocket ds = null;
		try {
			ss = new ServerSocket(port);
			ss.setReuseAddress(true);
			ds = new DatagramSocket(port);
			ds.setReuseAddress(true);
			return true;
		} catch (IOException e) {
		} finally {
			if (ds != null) {
				ds.close();
			}

			if (ss != null) {
				try {
					ss.close();
				} catch (IOException e) {
					/* should not be thrown */
				}
			}
		}

		return false;
	}

}